/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_BASE_STRING_HELP_H
#define ECMASCRIPT_BASE_STRING_HELP_H

#include <algorithm>
#include <codecvt>
#include <locale>
#include <regex>
#include <string>
#include <vector>

#include "plugins/ecmascript/runtime/ecma_string-inl.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "icu4c/source/common/unicode/unistr.h"
#include "libpandabase/utils/utf.h"
#include "libpandafile/file_items.h"

namespace ark::ecmascript::base {

// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static constexpr int UICODE_FROM_UTF8[] = {
    0x80, 0xc0, 0xdf, 0xe0, 0xef, 0xf0, 0xf7, 0xf8, 0xfb, 0xfc, 0xfd,
};
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static constexpr int UTF8_MIN_CODE[] = {
    0x80, 0x800, 0x10000, 0x00200000, 0x04000000,
};
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static constexpr char UTF8_FIRST_CODE[] = {
    0x1f, 0xf, 0x7, 0x3, 0x1,
};

enum PadPlacement { START, END };

enum TrimKind { TRIM_START, TRIM_END, TRIM_START_END };

class StringHelper {
public:
    static constexpr int INVALID_UNICODE_FROM_UTF8 = -1;
    PANDA_PUBLIC_API static std::string ToStdString(EcmaString *string);

    static bool CheckDuplicate(EcmaString *string);

    static inline bool Contains(const EcmaString *string, const EcmaString *other)
    {
        PandaString str = ConvertToPandaString(string, StringConvertedUsage::LOGICOPERATION);
        PandaString oth = ConvertToPandaString(other, StringConvertedUsage::LOGICOPERATION);
        PandaString::size_type index = str.find(oth);
        return (index != PandaString::npos);
    }

    static inline PandaString RepalceAll(PandaString str, const PandaString &oldValue, const PandaString &newValue)
    {
        if (oldValue.empty() || oldValue == newValue) {
            return str;
        }
        PandaString::size_type pos(0);
        while ((pos = str.find(oldValue, pos)) != PandaString::npos) {
            str.replace(pos, oldValue.length(), newValue);
            pos += newValue.length();
        }
        return str;
    }

    static inline std::string SubString(const JSHandle<EcmaString> &string, uint32_t start, uint32_t length)
    {
        return std::string(
            ConvertToPandaString(string.GetObject<EcmaString>(), start, length, StringConvertedUsage::LOGICOPERATION));
    }

    static inline std::u16string Utf16ToU16String(const uint16_t *utf16Data, uint32_t dataLen)
    {
        auto *char16tData = reinterpret_cast<const char16_t *>(utf16Data);
        std::u16string u16str(char16tData, dataLen);
        return u16str;
    }

    static inline std::string Utf8ToString(const uint8_t *utf8Data, uint32_t dataLen)
    {
        auto *charData = reinterpret_cast<const char *>(utf8Data);
        std::string str(charData, dataLen);
        return str;
    }

    static inline std::u16string Utf8ToU16String(const uint8_t *utf8Data, uint32_t dataLen)
    {
        auto *charData = reinterpret_cast<const char *>(utf8Data);
        std::string str(charData, dataLen);
        std::u16string u16str = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> {}.from_bytes(str);
        return u16str;
    }

    static inline std::string WstringToString(const std::wstring &wstr)
    {
        return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> {}.to_bytes(wstr);
    }

    static inline std::wstring StringToWstring(const std::string &str)
    {
        return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> {}.from_bytes(str);
    }

    static inline std::string U16stringToString(const std::u16string &u16str)
    {
        return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> {}.to_bytes(u16str);
    }

    static inline std::u16string StringToU16string(const std::string &str)
    {
        return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> {}.from_bytes(str);
    }

    static inline size_t Find(const std::string &thisStr, const std::string &searchStr, int32_t pos)
    {
        size_t idx = thisStr.find(searchStr, pos);
        return idx;
    }

    static inline size_t Find(const std::u16string &thisStr, const std::u16string &searchStr, int32_t pos)
    {
        size_t idx = thisStr.find(searchStr, pos);
        return idx;
    }

    static inline size_t RFind(const std::u16string &thisStr, const std::u16string &searchStr, int32_t pos)
    {
        size_t idx = thisStr.rfind(searchStr, pos);
        return idx;
    }

    static inline EcmaString *ToUpper(JSThread *thread, const std::u16string &str)
    {
        ecmascript::ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
        std::u16string tmpStr = str;
        const char16_t *constChar16tData = tmpStr.data();
        icu::UnicodeString uString(constChar16tData);
        icu::UnicodeString up = uString.toUpper();
        std::string res;
        up.toUTF8String(res);
        return *factory->NewFromStdString(res);
    }

    static inline EcmaString *ToLower(JSThread *thread, const std::u16string &str)
    {
        ecmascript::ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
        std::u16string tmpStr = str;
        const char16_t *constChar16tData = tmpStr.data();
        icu::UnicodeString uString(constChar16tData);
        icu::UnicodeString low = uString.toLower();
        std::string res;
        low.toUTF8String(res);
        return *factory->NewFromStdString(res);
    }

    static inline size_t FindFromU16ToUpper(const std::u16string &thisStr, uint16_t *u16Data)
    {
        std::u16string tmpStr = Utf16ToU16String(u16Data, 1);
        const char16_t *constChar16tData = tmpStr.data();
        icu::UnicodeString uString(constChar16tData);
        icu::UnicodeString up = uString.toUpper();
        std::string res;
        up.toUTF8String(res);
        std::u16string searchStr = StringToU16string(res);
        size_t idx = Find(thisStr, searchStr, 0);
        return idx;
    }

    static EcmaString *Repeat(JSThread *thread, const std::u16string &thisStr, int32_t repeatLen, bool canBeCompress);

    static int UnicodeFromUtf8(const uint8_t *p, int maxLen, const uint8_t **pp)
    {
        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        int c = *p++;
        if (c < UICODE_FROM_UTF8[0]) {
            *pp = p;
            return c;
        }
        int l = 0;
        if (c >= UICODE_FROM_UTF8[1U] && c <= UICODE_FROM_UTF8[2U]) {         // 1 - 2: 0000 0080 - 0000 07FF
            l = 1;                                                            // 1: 0000 0080 - 0000 07FF Unicode
        } else if (c >= UICODE_FROM_UTF8[3U] && c <= UICODE_FROM_UTF8[4U]) {  // 3 - 4: 0000 0800 - 0000 FFFF
            l = 2;                                                            // 2: 0000 0800 - 0000 FFFF Unicode
        } else if (c >= UICODE_FROM_UTF8[5U] && c <= UICODE_FROM_UTF8[6U]) {  // 5 - 6: 0001 0000 - 0010 FFFF
            l = 3;                                                            // 3: 0001 0000 - 0010 FFFF Unicode
        } else if (c >= UICODE_FROM_UTF8[7U] && c <= UICODE_FROM_UTF8[8U]) {  // 7 - 8: 0020 0000 - 03FF FFFF
            l = 4;                                                            // 4: 0020 0000 - 03FF FFFF Unicode
            // NOLINTNEXTLINE(readability-magic-numbers)
        } else if (c == UICODE_FROM_UTF8[9U] || c == UICODE_FROM_UTF8[10U]) {  // 9 - 10: 0400 0000 - 7FFF FFFF
            l = 5;                                                             // 5: 0400 0000 - 7FFF FFFF Unicode
        } else {
            return INVALID_UNICODE_FROM_UTF8;
        }
        /* check that we have enough characters */
        if (l > (maxLen - 1)) {
            return INVALID_UNICODE_FROM_UTF8;
        }
        return FromUtf8(c, l, p, pp);
    }

    static int FromUtf8(int c, int l, const uint8_t *p, const uint8_t **pp)
    {
        uint32_t b;
        // NOLINTNEXTLINE(hicpp-signed-bitwise)
        c &= UTF8_FIRST_CODE[l - 1];
        for (int i = 0; i < l; i++) {
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            b = *p++;
            if (b < utf::UTF8_2B_SECOND || b >= utf::UTF8_2B_FIRST) {
                return INVALID_UNICODE_FROM_UTF8;
            }
            // NOLINTNEXTLINE(hicpp-signed-bitwise)
            c = (c << 6) | (b & utf::UTF8_2B_THIRD);  // 6: Maximum Unicode range
        }
        if (c < UTF8_MIN_CODE[l - 1]) {
            return INVALID_UNICODE_FROM_UTF8;
        }
        *pp = p;
        return c;
    }

    static EcmaString *Trim(JSThread *thread, const std::u16string &thisStr, TrimKind kind);

    static ark::ecmascript::JSTaggedValue StringPad(JSThread *thread, JSHandle<JSTaggedValue> obj,
                                                    JSHandle<JSTaggedValue> maxLength,
                                                    JSHandle<JSTaggedValue> fillString, PadPlacement placement);

    static inline std::u16string Append(const std::u16string &str1, const std::u16string &str2)
    {
        std::u16string tmpStr = str1;
        return tmpStr.append(str2);
    }

    static inline uint32_t Utf8ToU32String(const std::vector<uint8_t> &data)
    {
        std::string str(data.begin(), data.end());
        std::u32string u32str = std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> {}.from_bytes(str);
        auto u32data = reinterpret_cast<uint32_t *>(u32str.data());
        return *u32data;
    }

    static inline std::string Utf32ToString(uint32_t u32Data)
    {
        UChar32 charData = u32Data;
        icu::UnicodeString uString(charData);
        std::string res;
        uString.toUTF8String(res);
        return res;
    }
};
}  // namespace ark::ecmascript::base
#endif  // ECMASCRIPT_BASE_STRING_HELP_H
